/*
 * Copyright (c) 2012 Kent Ryhorchuk
 *
 * Use of this source code is governed by a MIT-style
 * license that can be found in the LICENSE file or at
 * https://opensource.org/licenses/MIT
 */
#include <stdarg.h>
#include <lk/reg.h>
#include <lk/debug.h>
#include <stdio.h>
#include <assert.h>
#include <lib/cbuf.h>
#include <kernel/thread.h>
#include <platform/debug.h>
#include <arch/ops.h>
#include <dev/uart.h>
#include <target/debugconfig.h>
#include <platform/lpc22xx.h>


#define UARTREG(base, reg)  (*REG32((base)  + (reg)))


#ifdef ENABLE_UART0
cbuf_t uart0_rx_buf;
#ifndef UART0_FLOWCONTROL
#define UART0_FLOWCONTROL 0
#endif
#ifndef UART0_BAUDRATE
#define UART0_BAUDRATE 115200
#endif
#ifndef UART0_RXBUF_SIZE
#define UART0_RXBUF_SIZE 64
#endif
#endif

#ifdef ENABLE_UART1
cbuf_t uart1_rx_buf;
#ifndef UART1_FLOWCONTROL
#define UART1_FLOWCONTROL 0
#endif
#ifndef UART1_BAUDRATE
#define UART1_BAUDRATE 115200
#endif
#ifndef UART1_RXBUF_SIZE
#define UART1_RXBUF_SIZE 64
#endif
#endif


#ifdef ENABLE_UART0
static void Usart0RxIrqCallback(void);
static void Usart0ErrIrqCallback(void);
#endif

#ifdef ENABLE_UART1
static void Usart1RxIrqCallback(void);
static void Usart1ErrIrqCallback(void);
#endif


static inline uintptr_t uart_to_ptr(unsigned int n) {
    switch (n) {
        default:
        case 0:
            return LPC22XX_UART0_BASE;
        case 1:
        	return LPC22XX_UART1_BASE;
    }
}


void uart_init_early(void) {
#ifdef ENABLE_UART0
	/* Enable RxD and TxD pins */
	//PINSEL0 |= U0PINS;

	/* 8 bits, no Parity, 1 Stop bit */
	//U0LCR = 0x83;
	UARTREG(uart_to_ptr(0), LPC22XX_UART_LCR_OFFSET) = 0x83;

	/* Setup Baudrate */
	//U0DLL = (PCLK/16/BAUD_RATE) & 0xFF;
	//U0DLM = ((PCLK/16/BAUD_RATE) >> 8) & 0xFF;
	UARTREG(uart_to_ptr(0), LPC22XX_UART_DLL_OFFSET) = (PCLK/16/UART0_BAUDRATE) & 0xFF;
	UARTREG(uart_to_ptr(0), LPC22XX_UART_DLM_OFFSET) = ((PCLK/16/UART0_BAUDRATE) >> 8) & 0xFF;

	/* DLAB = 0 */
	//U0LCR = 0x03;
	UARTREG(uart_to_ptr(0), LPC22XX_UART_LCR_OFFSET) = 0x03;

#endif
#ifdef ENABLE_UART1
	/* Enable RxD and TxD pins */
	//PINSEL0 |= U0PINS;

	/* 8 bits, no Parity, 1 Stop bit */
	//U0LCR = 0x83;
	UARTREG(uart_to_ptr(1), LPC22XX_UART_LCR_OFFSET) = 0x83;

	/* Setup Baudrate */
	//U0DLL = (PCLK/16/BAUD_RATE) & 0xFF;
	//U0DLM = ((PCLK/16/BAUD_RATE) >> 8) & 0xFF;
	UARTREG(uart_to_ptr(1), LPC22XX_UART_DLL_OFFSET) = (PCLK/16/UART0_BAUDRATE) & 0xFF;
	UARTREG(uart_to_ptr(1), LPC22XX_UART_DLM_OFFSET) = ((PCLK/16/UART0_BAUDRATE) >> 8) & 0xFF;

	/* DLAB = 0 */
	//U0LCR = 0x03;
	UARTREG(uart_to_ptr(1), LPC22XX_UART_LCR_OFFSET) = 0x03;
#endif
}

void uart_init(void) {

#ifdef ENABLE_UART0
	cbuf_initialize(&uart0_rx_buf, UART0_RXBUF_SIZE);

#endif
#ifdef ENABLE_UART1
    cbuf_initialize(&uart1_rx_buf, UART1_RXBUF_SIZE);

#endif
}

void uart_rx_irq(M4_USART_TypeDef *usart, cbuf_t *rxbuf) {
    arm_cm_irq_entry();

    bool resched = false;
    while (Set == USART_GetStatus(usart, UsartRxNoEmpty)) {
        if (!cbuf_space_avail(rxbuf)) {
            // Overflow - let flow control do its thing by not
            // reading the from the FIFO.
			USART_FuncCmd(usart, UsartRxInt, Disable);
            break;
        }

        char c = USART_RecData(usart);
        cbuf_write_char(rxbuf, c, false);
        resched = true;
    }

    arm_cm_irq_exit(resched);
}

#ifdef ENABLE_UART1
static void Usart1RxIrqCallback(void)
{
	uart_rx_irq(M4_USART1, &uart1_rx_buf);
}

static void Usart1ErrIrqCallback(void)
{
  if (Set == USART_GetStatus(M4_USART1, UsartFrameErr)){
    USART_ClearStatus(M4_USART1, UsartFrameErr);
  }

  if (Set == USART_GetStatus(M4_USART1, UsartParityErr)){
    USART_ClearStatus(M4_USART1, UsartParityErr);
  }

  if (Set == USART_GetStatus(M4_USART1, UsartOverrunErr)){
    USART_ClearStatus(M4_USART1, UsartOverrunErr);
  }
}
#endif

#ifdef ENABLE_UART2
static void Usart2RxIrqCallback(void)
{
	uart_rx_irq(M4_USART2, &uart2_rx_buf);
}

static void Usart2ErrIrqCallback(void)
{
  if (Set == USART_GetStatus(M4_USART2, UsartFrameErr)){
    USART_ClearStatus(M4_USART2, UsartFrameErr);
  }

  if (Set == USART_GetStatus(M4_USART2, UsartParityErr)){
    USART_ClearStatus(M4_USART2, UsartParityErr);
  }

  if (Set == USART_GetStatus(M4_USART2, UsartOverrunErr)){
    USART_ClearStatus(M4_USART2, UsartOverrunErr);
  }
}
#endif



static void usart_putc(M4_USART_TypeDef *usart, char c) {
    while (USART_GetStatus(usart, UsartTxEmpty) == Reset);
    USART_SendData(usart, c);
    //while (USART_GetStatus(usart, UsartTxComplete) == Reset);
}

static int usart_getc(M4_USART_TypeDef *usart, cbuf_t *rxbuf, bool wait) {
    unsigned char c;
    if (cbuf_read_char(rxbuf, (char *) &c, wait) == 0)
        return -1;
    if (cbuf_space_avail(rxbuf) > cbuf_size(rxbuf))
		USART_FuncCmd(usart, UsartRxInt, Enable);

    return c;
}

static M4_USART_TypeDef *get_usart(int port) {
    switch (port) {
#ifdef ENABLE_UART1
        case 1:
            return M4_USART1;
#endif
#ifdef ENABLE_UART2
        case 2:
            return M4_USART2;
#endif
        default:
            ASSERT(false);
            return 0;
    }
}

static cbuf_t *get_rxbuf(int port) {
    switch (port) {
#ifdef ENABLE_UART1
        case 1:
            return &uart1_rx_buf;
#endif
#ifdef ENABLE_UART2
        case 2:
            return &uart2_rx_buf;
#endif
        default:
            ASSERT(false);
            return 0;
    }
}

int uart_putc(int port, char c) {
    M4_USART_TypeDef *usart = get_usart(port);
    usart_putc(usart, c);
    return 1;
}

int uart_getc(int port, bool wait) {
    cbuf_t *rxbuf = get_rxbuf(port);
    M4_USART_TypeDef *usart = get_usart(port);

    return usart_getc(usart, rxbuf, wait);
}

void uart_flush_tx(int port) {}

void uart_flush_rx(int port) {}

void uart_init_port(int port, uint baud) {
    M4_USART_TypeDef *usart = get_usart(port);

	USART_SetBaudrate(usart, baud);
}

// inject a character into the console uart rx buffer
void __debugger_console_putc(char c) {
    cbuf_t *rxbuf = get_rxbuf(DEBUG_UART);
    if (rxbuf && cbuf_space_avail(rxbuf)) {
        cbuf_write_char(rxbuf, c, false);
    }
}

